feat: inject core + relevant memories into long-horizon agent wake context - #2266
feat: inject core + relevant memories into long-horizon agent wake context#2266lsm wants to merge 2 commits into
Conversation
Factor the `## Core Memories` + `## Relevant Memories` rendering out of `buildCustomAgentTaskMessage` into an exported `buildMemorySectionLines` helper so worker kickoffs and (upcoming) long-horizon wakes reuse the same formatting and char budgets (core ~2000 chars, each relevant ~500). No behavior change for worker kickoff; existing memory tests stay green.
Long-horizon agents started cold on every wake unless they manually called memory_search, breaking continuity across their lifetime. Mirror the worker kickoff injection: prepend a `## Core Memories` (listCoreMemories, top-10) + `## Relevant Memories` (search by wake message text, top-5) block to every injected wake message via the shared `buildMemorySectionLines` helper. Centralized in `injectLongTermAgentMessage` so all wake paths (external events, peer messages, inbox flush) carry memory context. The wake message text is the search query, so relevant — not just core — memories surface. When no memories are stored the wake message is injected unchanged. Char budgets stay consistent with workers via the shared helper.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
lsm
left a comment
There was a problem hiding this comment.
🤖 Review by glm-5.1[1m] (NeoKai)
Model: glm-5.1[1m] | Client: NeoKai | Provider: Zhipu GLM
Recommendation: APPROVE — zero P0–P3 blocking findings. The implementation is correct, complete, well-tested, and faithfully mirrors the worker kickoff; all failure modes are mitigated. Approving as the PR author (so this posts as a COMMENT, not an APPROVE event).
What I reviewed
- Both commits (313 additions, 4 files):
refactorextractsbuildMemorySectionLines;featcentralizes the prepend ininjectLongTermAgentMessage. - Full changed files + worker-kickoff parity (
task-agent-manager.ts:821-850) +AgentMemoryRepositorysignatures + embedder wiring + all 3 callers ofinjectLongTermAgentMessage(external event, peer message, inbox flush) and their upstream catch boundaries. - Ran the two affected test files: 72 pass, 0 fail.
Correctness ✓
buildMemorySectionLinesextraction is behavior-preserving — worker output is byte-identical (it now doessections.push(...buildMemorySectionLines(...))).- LH wake prepends
## Core Memories(top-10) +## Relevant Memories(top-5) with the wake message text as the search query (space-runtime-service.ts:531-532) — matches the spec's "relevant, not just core" intent. - All three wake paths funnel through
injectLongTermAgentMessage, so memory context is consistent across external events, peer messages, and inbox flushes. - Empty-memory case handled cleanly:
lines.length === 0→''→ message injected unchanged (verified by test). - Leading blank-line separator is correctly trimmed via
.join('\n').trim()since the block is prepended, not appended to asections[]array.
Parity & budgets ✓
- Identical to worker kickoff:
listCoreMemories(spaceId, 10)+search(spaceId, query, 5); char budgets (core ~2000, each relevant ~500) shared via the same helper. No regression to worker injection.
Error handling ✓
AgentMemoryRepository.searchdegrades to[]viafallbackToNull(embedder failure → FTS-only →[]);listCoreMemoriesonly throws on SQLite hard errors. Traced all three callers: external events retry with bounded attempts (space-runtime.ts), peer messages become a structuredfaileddelivery record (messaging-adapter.ts), inbox flush has a per-message try/catch. The worker kickoff path is actually less defensive than this. No new risk.
Tests ✓
- Helper: empty input, core-before-relevant ordering, core-block char budget, per-relevant truncation.
- Runtime: full prepended block, section ordering, wake message trails the block, exact
listCoreMemories(id, 10)/search(id, 'event payload', 5)args, and the no-memories unchanged path.
Non-blocking design notes (P2 suggestions — not defects, consequences of the explicit spec)
-
Core-memory re-injection accumulates in context. Core memories are stable and space-scoped, but are now prepended + persisted on every wake via
saveUserMessage. SDK auto-compaction (query-options-builder.ts) eventually reclaims them, so it's not a hard ceiling, but on a 200k-window model ~100 wakes produce ~150k tokens of duplicated memory text, accelerating compaction cycles. The natural split would be core → system promptappend(once per session), relevant → per-wake user message. This is exactly what the spec asked for ("mirror worker kickoff at every wake"), so approving as-is — flagging as a worthwhile follow-up. -
Latency is acceptable. The per-wake search uses the local in-process ONNX embedder (
TransformersAgentMemoryEmbedder, granite-embedding-small q4, 384-dim) — a local CPU inference + SQLite FTS, not a network round-trip. Modest, best-effort, degrades gracefully, and is parity with worker kickoff.
Nice clean two-commit split (refactor → feat) and good test coverage. Ship it.
Long-horizon agents started cold on every wake (external event, reminder, or peer message) unless they manually called
memory_search, breaking continuity across their lifetime. This mirrors the worker kickoff injection: every wake message now gets a## Core Memories(top-10) +## Relevant Memories(top-5) block prepended, using the wake message text as the search query so relevant—not just core—memories surface.Implementation is centralized in
injectLongTermAgentMessageso all three wake paths (external events,deliverToLongTermAgent, inbox flush) carry memory context, and reuses a newly-factoredbuildMemorySectionLineshelper so formatting and char budgets (core ~2000 chars, each relevant ~500) stay identical to worker kickoff. When the space has no memories, the wake message is injected unchanged.Closes the worker/LH asymmetry: workers get memories at kickoff; LH agents now get them at every wake.